#! /usr/bin/env python

# Description: mapper_lfm_artist.py implements class that retrieve all attributes in artist Last.FM document.
# Colaborators: Saulo Ricci
# Date: 2010/08/26

from threading import Thread
import xml.dom.minidom
import types
import string

class mapper_lfm_artist:
	def __init__(self, xml_filename_list, output_dir, vocabulary_filename):
		self.output_dir= output_dir
		self.output_file_list = []
		self.xml_file_list = []
		for xml_filename in xml_filename_list:
			self.xml_file_list.append(xml.dom.minidom.parse(xml_filename))
		
		# Loads dict that contains lastfm vocabullary
		self.vocabulary = dict()
		with open(vocabulary_filename, 'r') as file:
			for entry in file:
				splits = entry.strip().split(" ")
				self.vocabulary[splits[0].strip()] = splits[1].strip()
		self.keys_voc = self.vocabulary.keys()
		self.keys_voc.sort()
		
		self.name_list = []
		self.tags_list = []
		self.wikiText_list = []
		for xml_file_object in self.xml_file_list:
			name_element = self.get_node_element("name", xml_file_object)
			name = None
			if name_element.__class__ != types.NoneType:
				if len(name_element) > 0:
					name = name_element[0].nodeValue
			self.name_list.append(name)
		
			tags = []
			tags_element = self.get_node_element("tags", xml_file_object)
			if tags_element.__class__ != types.NoneType:
				for element in tags_element:
					if element.nodeType == element.ELEMENT_NODE and element.localName == "string":
						tags.append(element.childNodes[0].nodeValue)
			self.tags_list.append(tags)

			wikiText_element = self.get_node_element("wikiText", xml_file_object)
			wikiText = None
			if wikiText_element.__class__ != types.NoneType:
				if len(wikiText_element) > 0:
					wikiText = wikiText_element[0].nodeValue
			self.wikiText_list.append(wikiText)
		
		self.make_modifications()
		self.output_data()

	def get_node_element(self, att_name, xml_file_object):
		element = None
		found = False
		for parent_element in xml_file_object.childNodes:
			for inner_element in parent_element.childNodes:
				if inner_element.nodeType == inner_element.ELEMENT_NODE and inner_element.localName == att_name:
					element = inner_element
					found = True
					break
			if found == True:
				break
		if found == True:
			return element.childNodes
		else:
			return element

	def make_modifications(self):
		for index in range(len(self.name_list)):
			if self.name_list[index].__class__ != types.NoneType:
				self.name_list[index] = self.name_list[index].lower().replace("&quot", "")
				for c in string.punctuation:
					if c != '-':
						self.name_list[index] = self.name_list[index].replace(c, "")

		for index in range(len(self.tags_list)):
			for i in range(len(self.tags_list[index])):
				self.tags_list[index][i] = self.tags_list[index][i].lower().replace("&quot", "")
				for c in string.punctuation:
					if c != '-':
						self.tags_list[index][i] = self.tags_list[index][i].replace(c, "")
				# If found composed tag, replace " " by "-"
				self.tags_list[index][i] = self.tags_list[index][i].strip()
				self.tags_list[index][i] = self.tags_list[index][i].replace(" ", "-")

		for index in range(len(self.wikiText_list)):
			if self.wikiText_list[index].__class__ != types.NoneType:
				self.wikiText_list[index] = self.wikiText_list[index].lower().replace("&quot", "")
				for c in string.punctuation:
					if c != '-':
						self.wikiText_list[index] = self.wikiText_list[index].replace(c, "")

	def output_data(self):
		for index in range(len(self.xml_file_list)):
			# ID
			id = self.map_id(self.name_list[index])
			with open(self.output_dir + "/test." + id.strip(), 'w') as test_file:
				with open(self.output_dir + "/inputtag." + id.strip(), 'w') as input_tag_file:
					self.output_file_list.append((self.output_dir + "/test." + id.strip() ,self.output_dir + "/inputtag." + id.strip()))
					tags = self.map_tags(self.tags_list[index])

					description_parts = self.split_description(self.wikiText_list[index])
	
					description_threaded_mappers = []
					for i in range(len(description_parts)):
						description_threaded_mappers.append(threaded_mapper_description(description_parts[i], self.vocabulary, self.keys_voc))
					for j in range(len(description_threaded_mappers)):
						description_threaded_mappers[j].start()
					description = ""
					for k in range(len(description_threaded_mappers)):
						description_threaded_mappers[k].join()
						description += description_threaded_mappers[k].mapped_description + " "
					description = description.strip()
					title = self.map_title(self.name_list[index])	
					test_file.write("ID " + id + " | TAG" + tags + " | DESCRIPTION " + description + " | TITLE" + title + "\n")
					input_tag_file.write("ID " + id + tags + "\n")

	def split_description(self, description):
		description_parts = []
		description_splits = description.split(" ")
		while True:
			description_part = ""
			for index in range(200):
				if len(description_splits) > 0:
					description_part += description_splits.pop(0) + " "
				else:
					break
			if description_part != "":
				description_parts.append(description_part.strip())
			else:
				break

		return description_parts		

	def binary_search(self, value, seq):
		final_index = len(seq) - 1
		start_index = 0
		current_index = (final_index - start_index)/2
		while (final_index != start_index) and ((final_index - start_index) > 1):
			if seq[current_index] == value: 
				return True
			elif seq[current_index] < value:
				start_index = current_index + 1
				current_index = ((final_index - start_index)/2) + start_index
			else:
				final_index = current_index - 1
				current_index = ((final_index - start_index)/2) + start_index

		if seq[current_index] == value:
			return True
		elif (seq[start_index] == value) or (seq[final_index] == value):
			return True
		return False

	def map_id(self, id):
		id_str = ""
		if id.__class__ != types.NoneType:
			id_str += id.strip().replace(" ", "+")
		return id_str

	def map_tags(self, tags):
		tags_str = ""
		for tag in tags:
			if self.binary_search(tag, self.keys_voc):
				tags_str += " " + self.vocabulary[tag].strip()
		return tags_str

	def map_title(self, title):
		title_str = ""
		title_split = title.split(" ")
		for word in title_split:
			if self.binary_search(word.strip(), self.keys_voc):
				title_str += " " + self.vocabulary[word.strip()].strip()
		return title_str	

class threaded_mapper_description(Thread):
	
	def __init__(self, description, vocabulary, keys_voc):
		Thread.__init__(self)
		self.description = description
		self.vocabulary = vocabulary
		self.keys_voc = keys_voc
		self.mapped_description = ""
	
	def run(self):
		self.mapped_description = self.map_description()

	def map_description(self):
		description_str = ""
		description_splits = self.description.split(" ")
		for description_word in description_splits:
			if self.binary_search(description_word.strip(), self.keys_voc):
				description_str += " " + self.vocabulary[description_word.strip()].strip()
		return description_str

	def binary_search(self, value, seq):
		final_index = len(seq) - 1
		start_index = 0
		current_index = (final_index - start_index)/2
		while (final_index != start_index) and ((final_index - start_index) > 1):
			if seq[current_index] == value: 
				return True
			elif seq[current_index] < value:
				start_index = current_index + 1
				current_index = ((final_index - start_index)/2) + start_index
			else:
				final_index = current_index - 1
				current_index = ((final_index - start_index)/2) + start_index

		if seq[current_index] == value:
			return True
		elif (seq[start_index] == value) or (seq[final_index] == value):
			return True
		return False
